<?php
define("TESTS_LIB", LIB."TsT".DIRECTORY_SEPARATOR);
define("TESTS_APP", APP."TsT".DIRECTORY_SEPARATOR);

define("LIB_STUBS", TESTS_LIB. "stubs". DIRECTORY_SEPARATOR);
define("APP_STUBS", TESTS_APP. "stubs". DIRECTORY_SEPARATOR);

class Tst_Unit{

	public function __construct(){

	}
}

class StaticStorage{
	public static $storage = array();
	private static $instance = null;

	private function __construct(){
	}

	public function __set($key, $value){
		self::set($key, $value);
	}

	public static function set($key, $value){
		self::$storage[$key] = $value;
	}

	public function __get($key){
		return self::get($key);
	}

	public static function get($key){
		return self::$storage[$key];
	}

	public function __isset($key){
		return isset(self::$storage[$key]);
	}

	public static function instance(){
		if(null == self::$instance){
			self::$instance = new StaticStorage();
		}

		return self::$instance;
	}
}

class AssertException extends Exception{}

class TestRunner{

	public function setUp(){}
	public function tearDown(){}
	
	public function run(){
		$methods = array();
		$className = get_class($this);
		foreach(get_class_methods($this) as $method){
			if(0 === strpos($method, "test_")){
				$methods[] = $method;
			}
		}

		$numberOfTests = count($methods);
		$completed = 0;
		$failed = 0;
		$errors = 0;

		echo "<pre>\n";
		echo "Number of tests: {$numberOfTests}\n";
		$this->setUp();
		foreach($methods as $method){
			try{
				echo "\tRunning test: {$className}->{$method}\n";
				$this->$method();

				$completed++;
			}catch(AssertException $ae){
				echo "Assertion failed:\n\n{$ae->getMessage()}\n\n{$ae->getTraceAsString()}\n\n";

				$failed++;
			}catch(Exception $e){
				echo "Exception {$e} occured\n";

				$errors++;
			}
		}
		$this->tearDown();
		echo "Completed/Failed/Errors ({$completed}/{$failed}/{$errors})\n";
		echo "</pre>";
	}
	
	private static function parseToString($input, $removeLineEnds = true){
		$input = is_array($input)  ? $removeLineEnds? str_replace("\n", "", var_export($input, true))
													: var_export($input, true)
													: $input;
		
		$input = is_object($input) ? var_export($input, true) : $input;
		
		return "{$input}";
	}

	protected static function assertTrue($actual, $message = false){
		if(true !== $actual){
			throw new AssertException(false === $message ?  "true !=== '{$actual}'" : $message);
		}
	}
	
	protected static function assertFalse($actual, $message = false){
		if(false !== $actual){
			throw new AssertException(false === $message ? "false !=== '{$actual}'" : $message);
		}
	}
	
	protected static function assertEqual($left, $right){
		$left = self::parseToString($left);
		$right = self::parseToString($right);
		
		self::assertTrue($left === $right, "The following two lines do not match:\n1:'{$left}'\n2:'{$right}'");
	}
	
	protected static function assertNotEqual($left, $right){
		self::assertTrue($left !== $right, "'{$left}' === '{$right}'");
	}

	protected static function assertInstanceOf($actual, $expected){
		if(! $actual instanceof $expected){
			$type =get_class($actual);
			throw new AssertException("{$type} not an instance of {$expected}");
		}
	}

	protected static function assertThrowCall($expectedException, $callee){
		try{
			if(is_array($callee)){
				$callee[0]->{$callee[1]}();
			}else{
				$callee();
			}
			
			throw new AssertException("Exception not thrown");
		}catch(AssertException $ae){
			throw $ae;
		}catch(Exception $e){
			if(! $e instanceof $expectedException){
				$type = get_class($e);
				throw new AssertException("{$type} not an instance of {$expectedException}");
			}
		}
	}

	protected static function assertReturnNull($underTest){
		list($object,$method) = $underTest;

		$retval = $object->$method();

		if(null !== $retval){
			throw new AssertException("{$retval} is not equal to null");
		}
	}

	protected static function assertReturnValue($underTest, $expected){
		list($object,$method) = $underTest;

		$retval = $object->$method();

		$expected = var_export($expected, true);
		$retval = var_export($retval, true);
		
		if($retval !== $expected){
			throw new AssertException("{$retval} is not equal to {$expected}");
		}
	}
}

function loadRunners($directory){
	$runners = array();
	
	foreach(glob($directory . "*Tester.php") as $filename){
		$runners[] = include($filename);
	}
	
	return $runners;
}

function runTests(){
	foreach(array_merge(loadRunners(TESTS_LIB),	loadRunners(TESTS_APP)) as $runner){
		$runner->run();
	}
}